接續上一篇RestController
轉成Reactive,可以看到邏輯的部分已經被抽到Handler
內了,剩下就是路徑轉導就是交由Router
來處理。
首先看RouterFunctions.java
,大部分s結尾的都是工具類別,像是Collections
、Arrays
,RouterFunctions
內有提供route
,傳入predicate
&handlerFunction
,很直覺得理解就是符合predicate
條件則交由handlerFunction
處理,這邊predicate
是RequestPredicate
,傳入的參數就是ServerRequest
。
public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
return new DefaultRouterFunction<>(predicate, handlerFunction);
}
很直覺得轉換後,將原本的annotation-based
的寫法轉換如下,包含了路徑與HttpMethod
,ServerRequest
本身有提供取得路徑(path()
,與method()
可以取得HttpMethod
。
RouterFunctions.route(request ->
request.path().equals("/router/greeting") && HttpMethod.GET.equals(request.method()), greetingHandler::allGreeting);
接下來有一個好用的工具類別RequestPredicates.java
裡面可以看到很多方便的function
,上面的條件判斷都可以被簡化,像是比對HttpMethod
的method()
,比對路徑的path()
,就像是整合後的@GetMapping()
一樣,同樣有一個GET()
整合了path
跟get
,對於header
也都有支援,contentType
、accept
,這些條件(RequestPredicate
)可以透過RequestPredicate.java
內的default function (and()
)來串接,剩餘其他好用的方法,之後有遇到再來介紹。
public abstract class RequestPredicates {
...
public static RequestPredicate method(HttpMethod httpMethod) {
return new HttpMethodPredicate(httpMethod);
}
public static RequestPredicate path(String pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
}
public static RequestPredicate GET(String pattern) {
return method(HttpMethod.GET).and(path(pattern));
}
public static RequestPredicate contentType(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
return new ContentTypePredicate(mediaTypes);
}
public static RequestPredicate accept(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
return new AcceptPredicate(mediaTypes);
}
...
}
經過整理後,再將另外兩個也一併改寫,再透過RouterFunction.java
內的default function (and()
)來串聯(是不是跟上面RequestPredicate
有異曲同工之妙)。
RouterFunctions
.route(GET("/router/greeting/{id}"), greetingHandler::getGreeting);
RouterFunctions
.route(POST("/router/greeting").and(contentType(APPLICATION_JSON)), greetingHandler::saveGreeting);
RouterFunctions
.route(GET("/router/greeting"), greetingHandler::allGreeting);
////////////////////////////////
RouterFunctions.route(GET("/router/greeting/{id}"), greetingHandler::getGreeting)
.and(
RouterFunctions.route(
POST("/router/greeting").and(contentType(APPLICATION_JSON)),
greetingHandler::saveGreeting))
.and(RouterFunctions.route(GET("/router/greeting"), greetingHandler::allGreeting));
再來可以很明顯可以看出and
之後都是接route
,而且沒有一個像是掛在class
層的@RequestMapping()
可以把相同的路徑往上抽,所以還差最後的一步驟整合
RouterFunction.java 幫你整合好的andRoute
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
return and(RouterFunctions.route(predicate, handlerFunction));
}
RouterFunctions.java nest()
,他可以把整個共同的部分統一在第一個參數(predicate
),後面就放相同類型的routerFunction
public static <T extends ServerResponse> RouterFunction<T> nest(
RequestPredicate predicate, RouterFunction<T> routerFunction) {
return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}
成果如下,將相同的/router/greeting
往上抽到第一層nest
,兩個get
都需要有accept,抽到第二層的nest
,最後Post
與accept
無關所以並不在第二層nest
範圍內。
GreetingRouter.java
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> routerFunction(GreetingHandler handler) {
return nest(
path("/router/greeting"),
nest(
accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getGreeting)
.andRoute(method(HttpMethod.GET), handler::allGreeting))
.andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::saveGreeting));
}
}
經過這兩天的學習,大致上能掌握將controller
轉換成reactive的方式,相信大部分的情境應該都可以在介紹的工具類別裡面找到方法處理。